单个参数的传递

通过#{参数名}取出参数值,实际上在传递单个参数的时候,MyBatis不会做特殊处理,实际上#{}内部的参数名你可以任意取值,但还是建议和接口中参数的名字保持一致。

多个参数的传递

MyBatis遇见多个参数时会进行特殊处理,多个参数会被封装成一个Map#{}就是从Map中获取指定key的值,key的值为param1...paramN,而value才是我们传入的值。要想从传入的Map中取值,只能通过#{param1}#{param2}…或者#{0}#{1}

1
public Employee getEmpByIdAndLastName(Integer id, String lastName);

其对应的SQL映射为:

1
2
3
<select id="getEmpByIdAndLastName" resultType="com.glemontree.mybatis.bean.Employee">
select * from tbl_employee where id = #{param1} and last_name = #{param2}
</select>

然后进行测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Test
public void test04() throws IOException {
// 获取SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
// 根据SqlSessionFactory获取SqlSession对象
// openSession()方法不会自动提交,需要自己手动提交
// openSession(true)方法会进行自动提交
SqlSession openSession = sqlSessionFactory.openSession();
// 获取接口的实现类对象
// MyBatis会为接口自动的创建一个代理对象,代理对象去执行增删改查
try {
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
Employee employee = mapper.getEmpByIdAndLastName(1, "tom");
System.out.println(employee);
} finally {
openSession.close();
}
}

这种方法虽然可以使用,但是不够直观,因此,通常使用命名参数:明确指定封装参数时Mapkey,不要再使用param1、param2...,可以使用@Param注解来完成。

1
public Employee getEmpByIdAndLastName(@Param("id") Integer id, @Param("lastName") String lastName);

这样,在封装Map时所使用的key就是id和lastName。

另一方面,如果参数很多,正好是业务逻辑的数据模型,我们就可以直接传入POJO,此时使用#{属性名}就可以取出传入的POJO属性值。

如果多个参数不是业务逻辑的数据模型,没有对应的POJO,为了方便,我们也可以传入Map,此时#{key}就是取出Map中对应的值。

1
public Employee getEmpByMap(Map<String, Object> map);
1
2
3
<select id="getEmpByMap" resultType="com.glemontree.mybatis.bean.Employee">
select * from tbl_employee where id = #{id} and last_name = #{lastName}
</select>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Test
public void test04() throws IOException {
// 获取SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
// 根据SqlSessionFactory获取SqlSession对象
// openSession()方法不会自动提交,需要自己手动提交
// openSession(true)方法会进行自动提交
SqlSession openSession = sqlSessionFactory.openSession();
// 获取接口的实现类对象
// MyBatis会为接口自动的创建一个代理对象,代理对象去执行增删改查
try {
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
Map<String, Object> map = new HashMap<String, Object>();
map.put("id", 1);
map.put("lastName", "tom");
Employee employee = mapper.getEmpByMap(map);
} finally {
openSession.close();
}
}

如果多个参数不是业务模型中的数据,但是经常要使用,推荐编写一个TO(Transfer Object)数据传输对象。比如分页查询的时候封装一个Page

1
2
3
4
Page {
int index;
int size;
}

需要注意的是,如果接口参数类型为Collection(List、Set)类型或者是数组也会特殊处理,也是把传入的List或者数组封装在Map中,若为Collectionkey使用的是collection,若为Listkey还可以使用list;若为数组,则key使用的是array,例如:

假设接口如下:

1
public Employee getEmpById(List<Integer> ids);

则取值的时候取出第一个id的值需要使用#{list[0]}

参数值的获取

#{}可以获取Map中的值或者POJO对象属性的值,对于MyBatis来说,还支持${}的取值方式。两者的区别在于:#{}是以预编译的形式将参数设置到SQL语句中,可以防止SQL注入,而${}取出的值直接拼装在SQL语句中,会有安全问题。

大多情况下我们取参数的值都应该使用#{},对于原生JDBC不支持占位符的地方就可以使用${}进行取值。举个例子:

分表操作,按照年份分表拆分:

1
select * from ${year}_salary where xxx;

原生JDBC是不能如下操作的:

1
select * from ? where xxx;

此时可以使用${}进行取值。

再比如排序:

1
select * from tbl_employee order by ${f_name} ${order};

同样地,原生JDBC不支持order by后面使用占位符。

参数处理

  • 参数可以指定一个特殊的数据类型:

    1
    2
    #{property, javaType=int, jdbcType=NUMERIC}
    #{height, javaType=double, jdbcType=NUMERIC, numericScale=2}
    • javaType通常可以从参数对象中来确定
    • 如果null被当做值来传递,对于所有可能为空的列,jdbcType需要被设置
    • 对于数值类型,还可以设置小数点后保留的位数
    • mode属性(存储过程)允许指定INOUTINOUT参数,如果参数为OUTINOUT,参数对象属性的真实值将会被改变
  • jdbcType通常需要在某种特定的条件下被设置:在我们数据为null的时候,有些数据库可能不能识别MyBatisnull的默认处理比如OracleOracle会报JdbcType OTHER无效的类型错误。

    MyBatis对所有的null映射的是原生JdbcOTHER类型,Oracle不能正确处理。

    此时可以做如下设置:

    1
    #{email, jdbcType=NULL}

    这是因为全局配置中jdbcTypeForNull=OTHEROracle不支持,因此主要有以下两种解决办法:

    • #{email, jdbcType=OTHER}

      这种解决方法只会影响当前SQL语句的jdbcType

    • 更改全局配置中jdbcTypeForNull,将其改为NULL即可,如下:

      1
      2
      3
      <settings>
      <setting name="jdbcTypeForNull" value="NULL"/>
      </settings>